//
// See the file COPYING for redistribution information.
//

#include <stdint.h>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <Naipax/Common.hpp>
#include <Naipax/Error.hpp>
#include <Naipax/Ppq.hpp>

using namespace std;
using namespace Naipax;

template <typename DataType, typename Serializer>
Ppq<DataType, Serializer>::Ppq(const char *pPathname, uint32_t PriorityLevels)
        : _mPriorityLevels(PriorityLevels) {
}

template <typename DataType, typename Serializer>
Ppq<DataType, Serializer>::~Ppq() {
}

template <typename DataType, typename Serializer>
int32_t Ppq<DataType, Serializer>::Enqueue(const DataType &rData, 
                                           uint32_t Level) {
    return 0;
}

template <typename DataType, typename Serializer>
int32_t Ppq<DataType, Serializer>::Dequeue(vector<DataType> &rData, 
                                           uint32_t MaxItems) {
    return 0;
}

// specialized implementation for std::string as DataType
Ppq<string>::Ppq(const char *pPathname, uint32_t PriorityLevels)
        : _mPriorityLevels(PriorityLevels) {
    if (pPathname == NULL || PriorityLevels == 0) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }

    // Try to create pPathname first
    if (access(pPathname, F_OK) != 0) {
        if (mkdir(pPathname, 0755) != 0) {
            NAIPAX_THROW(Error::CANNOT_MKDIR);
        }
    }

    // Set up databases
    _mpDbs = new Db*[PriorityLevels];
    for (uint32_t i = 0; i < PriorityLevels; ++i) {
        _mpDbs[i] = new Db(NULL, 0);
        char Dbname[32];
        snprintf(Dbname, 32, "%s/%u", pPathname, i);
        if (_mpDbs[i]->open(NULL, Dbname,
                            NULL, DB_RECNO, DB_CREATE, 0644) != 0) {
            NAIPAX_THROW(Error::CANNOT_OPEN_DB);
        }
    }
}

Ppq<string>::~Ppq() {
    for (uint32_t i = 0; i < _mPriorityLevels; ++i) {
        if (_mpDbs[i]->close(0) != 0) {
            NAIPAX_THROW(Error::CANNOT_CLOSE_DB);
        }
        delete _mpDbs[i];
    }
    delete[] _mpDbs;
}

int32_t Ppq<string>::Enqueue(const string &rData, uint32_t Level) {
    if (Level >= _mPriorityLevels) {
        NAIPAX_THROW(Error::DATA_OUT_OF_RANGE);
    }
    if (rData.length() == 0) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }

    Dbt Key;
    Dbt Data((void*) rData.c_str(), rData.length());

    if (_mpDbs[Level]->put(NULL, &Key, &Data, DB_APPEND) != 0) {
        NAIPAX_THROW(Error::CANNOT_PUT_DATA);
    }

    return 0;
}

int32_t Ppq<string>::Dequeue(vector<string> &rData, uint32_t MaxItems) {
    uint32_t ItemCount = 0;

    rData.clear();
    // Get items, starting from the queue with the highest priority
    for (uint32_t i = 0; i < _mPriorityLevels; ++i) {
        if (ItemCount >= MaxItems) {
            break;
        }

        Dbc *pDbCursor = NULL;
        try {
            _mpDbs[i]->cursor(NULL, &pDbCursor, 0);
            
            Dbt Key;
            Dbt Data;
            while (pDbCursor->get(&Key, &Data, DB_NEXT) == 0) {
                ++ItemCount;
                rData.push_back(string((const char*) Data.get_data(), 
                                       Data.get_size()));
                if (pDbCursor->del(0) != 0) {
                    NAIPAX_THROW(Error::CANNOT_DELETE_DATA);
                }
                if (ItemCount >= MaxItems) {
                    break;
                }
            }
            if (pDbCursor != NULL) {
                if (pDbCursor->close() != 0) {
                    NAIPAX_THROW(Error::CANNOT_CLOSE_DB);
                }
            }
        }
        catch (DbException &dbe) {
            if (pDbCursor != NULL) {
                if (pDbCursor->close() != 0) {
                    NAIPAX_THROW(Error::CANNOT_CLOSE_DB);
                }
            }
            NAIPAX_THROW(Error::CANNOT_GET_DATA);
        }
    }

    return 0;
}
